﻿//------------------------------------------------------------------------------
//  Namespace: FruitVentDesign.Controls
//  
//  Function： N/A
//  Name： TreeSelect
//  
//  Ver       Time                     Author
//  0.10      2021/6/25 8:12:52      FruitVent
//
//  此代码版权归作者本人FruitVent所有
//  源代码使用协议遵循本仓库的开源协议及附加协议，若本仓库没有设置，则按MIT开源协议授权
//  CSDN博客：https://blog.csdn.net/weixin_39552347
//  源代码仓库：https://gitee.com/fruitvent
//  感谢您的下载和使用
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
using FruitVentDesign.Commands;
using FruitVentDesign.Utils;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Media;

namespace FruitVentDesign.Controls
{
    public class TreeSelect : ComboBox
    {
        static TreeSelect()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(TreeSelect), new FrameworkPropertyMetadata(typeof(TreeSelect)));
        }

        public TreeSelect()
        {
            Loaded += TreeSelect_Loaded;
        }

        private void TreeSelect_Loaded(object sender, RoutedEventArgs e)
        {
            PresetPlaceholderValue = Placeholder_TextBolck.Text;
        }

        private Popup popup;
        private Border PART_Placeholder;
        private TreeView ItemsHostTreeView;
        private TextBox PART_SearchTextBox;
        private TextBlock Placeholder_TextBolck;
        private ToggleButton PART_DropDownButton;
        public object PreviousSelectedItem { get; set; } = null;
        private string PresetPlaceholderValue { get; set; } = string.Empty;

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            popup = Template.FindName("Popup", this) as Popup;
            PART_Placeholder = Template.FindName("PART_Placeholder", this) as Border;
            ItemsHostTreeView = Template.FindName("ItemsHostTreeView", this) as TreeView;
            PART_SearchTextBox = Template.FindName("PART_SearchTextBox", this) as TextBox;
            PART_DropDownButton = Template.FindName("PART_DropDownButton", this) as ToggleButton;
            if (PART_Placeholder.Child is TextBlock placeholder)
            {
                Placeholder_TextBolck = placeholder;
            }

            PART_SearchTextBox.TextChanged += PART_EditableTextBox_TextChanged;
            PART_SearchTextBox.PreviewMouseLeftButtonUp += PART_EditableTextBox_PreviewMouseLeftButtonUp;
            PART_DropDownButton.PreviewMouseLeftButtonUp += PART_DropDownButton_PreviewMouseLeftButtonUp;
            popup.Closed += Popup_Closed;
            popup.Opened += Popup_Opened;
            ItemsHostTreeView.SelectedItemChanged += ItemsHostTreeView_SelectedItemChanged;
        }

        private void Popup_Opened(object sender, EventArgs e)
        {
            ItemsHostTreeView.IsEnabled = true;
        }

        #region events
        public event RoutedPropertyChangedEventHandler<object> SelectedItemChanged;

        protected void OnSelectedItemChanged(object oldValue, object newValue)
        {
            if (SelectedItemChanged == null) { return; }

            if (!ReferenceEquals(oldValue, newValue))
            {
                var args = new RoutedPropertyChangedEventArgs<object>(oldValue, newValue);
                SelectedItemChanged.Invoke(this, args);
            }
        }
        #endregion

        private void ItemsHostTreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            if (IsDropDownOpen)
            {
                // update infomation
                SelectedTreeItem = e.NewValue;
                // raise selected item change event
                OnSelectedItemChanged(e.OldValue, e.NewValue);
                // close the popup
                IsDropDownOpen = false;
                // disable the tree view(avoid selected item changed when popup closing)
                ItemsHostTreeView.IsEnabled = false;
            }
        }
        
        /// <summary>
        /// 输入框鼠标左键点击抬起事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PART_EditableTextBox_PreviewMouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if (!IsDropDownOpen)
            {
                IsDropDownOpen = true;
            }
        }

        /// <summary>
        /// 下拉框收起事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Popup_Closed(object sender, EventArgs e)
        {
            if (ShowSearch)
            {
                PART_SearchTextBox.Text = string.Empty;
                PART_SearchTextBox.Visibility = Visibility.Collapsed;
                IsReadOnly = true;
            }
        }

        /// <summary>
        /// 下拉按钮鼠标点击抬起事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PART_DropDownButton_PreviewMouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if (ShowSearch && IsDropDownOpen)
            {
                if (!string.IsNullOrEmpty(Text))
                {
                    Placeholder_TextBolck.Text = GetDisplay(SelectedTreeItem);
                }
                else
                {
                    Placeholder_TextBolck.Text = PresetPlaceholderValue;
                }

                PART_SearchTextBox.Visibility = Visibility.Visible;
                IsReadOnly = false;
                _ = PART_SearchTextBox.Focus();
            }
        }

        /// <summary>
        /// 输入框内容发生改变时
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PART_EditableTextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            if (sender is TextBox input)
            {

                ItemsHostTreeView.SelectedItemChanged -= ItemsHostTreeView_SelectedItemChanged;
                _ = DoFilter(ItemsHostTreeView, input.Text);
                ItemsHostTreeView.SelectedItemChanged += ItemsHostTreeView_SelectedItemChanged;
            }
        }

        /// <summary>
        /// 获得筛选结果
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        private TreeViewItem DoFilter(ItemsControl container, string text)
        {
            if (container != null)
            {
                if (container.DataContext == null) { return null; }

                PropertyInfo property = container.DataContext.GetType().GetProperty((string)SearchPath);
                string filter = string.Empty;
                if (property != null)
                {
                    filter = property.GetValue(container.DataContext).ToString();
                }

                if (!string.IsNullOrEmpty(text) && filter.ToLower().IndexOf(text.ToLower()) >= 0)
                {
                    if (container is TreeViewItem)
                    {
                        _ = container.ApplyTemplate();
                        ContentPresenter contentPresenter =
                            (ContentPresenter)container.Template.FindName("PART_Header", container);
                        contentPresenter.SetValue(FontWeightProperty, FontWeights.Bold);
                    }
                }
                else
                {
                    if (container is TreeViewItem)
                    {
                        _ = container.ApplyTemplate();
                        ContentPresenter contentPresenter =
                            (ContentPresenter)container.Template.FindName("PART_Header", container);
                        contentPresenter.SetValue(FontWeightProperty, FontWeights.Normal);
                    }
                }

                for (int i = 0, count = container.Items.Count; i < count; i++)
                {
                    TreeViewItem subContainer = (TreeViewItem)container.
                        ItemContainerGenerator.ContainerFromIndex(i);
                    if (subContainer != null)
                    {
                        // Search the next level for the object.
                        TreeViewItem resultContainer = DoFilter(subContainer, text);
                        if (resultContainer != null)
                        {
                            return resultContainer;
                        }
                    }
                }
            }

            return null;
        }

        #region custom seletedItem
        public static readonly DependencyProperty DisplayPathProperty = DependencyProperty.Register(
            "DisplayPath", typeof(string), typeof(TreeSelect), new FrameworkPropertyMetadata(string.Empty));

        public string DisplayPath
        {
            get { return (string)GetValue(DisplayPathProperty); }
            set { SetValue(DisplayPathProperty, value); }
        }

        public static readonly DependencyProperty SelectedTreeItemProperty = DependencyProperty.Register(
            "SelectedTreeItem", typeof(object), typeof(TreeSelect),
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Journal | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedTreeItemPropertyChanged));

        private static void OnSelectedTreeItemPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is TreeSelect self)
            {
                self.OnSelectedTreeItemPropertyChanged();
            }
        }

        private void OnSelectedTreeItemPropertyChanged()
        {
            Text = GetDisplay(SelectedTreeItem);
            SelectedValue = SelectedValuePath != null ? GetSelectedValue(SelectedValue) : GetDisplay(SelectedTreeItem);
        }

        /// <summary>
        /// GetDisplayMember
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        private string GetDisplay(object obj)
        {
            if (obj == null) { return string.Empty; }

            if (!string.IsNullOrEmpty(DisplayPath))
            {
                PropertyInfo property = obj.GetType().GetProperty(DisplayPath);
                return property == null ? "undefined-property" : property.GetValue(obj).ToString();
            }
            else
            {
                return obj.ToString();
            }
        }

        /// <summary>
        /// GetSelectedValue
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        private object GetSelectedValue(object obj)
        {
            if (obj == null) { return null; }

            if (!string.IsNullOrEmpty(SelectedValuePath))
            {
                PropertyInfo property = obj.GetType().GetProperty(SelectedValuePath);
                return property == null ? null : property.GetValue(obj);
            }
            else
            {
                return obj;
            }
        }

        public object SelectedTreeItem
        {
            get { return GetValue(SelectedTreeItemProperty); }
            set { SetValue(SelectedTreeItemProperty, value); }
        }
        #endregion

        #region HasErrorMessage 验证通过与否字段
        /// <summary>
        /// 验证通过与否字段
        /// </summary>
        public static readonly DependencyProperty HasErrorMessageProperty = DependencyProperty.Register(
            "HasErrorMessage", typeof(bool), typeof(TreeSelect), new FrameworkPropertyMetadata(false,
                FrameworkPropertyMetadataOptions.Journal | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                new PropertyChangedCallback(OnHasErrorMessagePropertyChanged)));

        private static void OnHasErrorMessagePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            var oldValue = (object)args.OldValue;
            var newValue = (object)args.NewValue;

            if (oldValue == newValue)
                return;

            if ((bool)newValue)
            {
                if (obj is FrameworkElement content && content.Parent is Form form)
                {
                    if (form.ItemContainerGenerator.ContainerFromItem(content) is FormItem formItem)
                    {
                        formItem.HasErrorMessage = true;
                        var binding = new Binding(nameof(FormItem.ErrorMessage))
                        {
                            Source = content,
                            Mode = BindingMode.TwoWay,
                            Path = new PropertyPath("(Validation.Errors).CurrentItem.ErrorContent")
                        };
                        formItem.SetBinding(FormItem.ErrorMessageProperty, binding);
                    }
                }
            }
            else
            {
                if (obj is FrameworkElement content && content.Parent is Form form)
                {
                    if (form.ItemContainerGenerator.ContainerFromItem(content) is FormItem formItem)
                    {
                        formItem.HasErrorMessage = false;
                    }
                }
            }
        }

        public bool HasErrorMessage
        {
            get { return (bool)GetValue(HasErrorMessageProperty); }
            set { SetValue(HasErrorMessageProperty, value); }
        }

        public static void SetHasErrorMessage(DependencyObject element, bool value)
        {
            element.SetValue(HasErrorMessageProperty, value);
        }

        public static bool GetHasErrorMessage(DependencyObject element)
        {
            return (bool)element.GetValue(HasErrorMessageProperty);
        }
        #endregion

        #region PlaceholderProperty 占位符
        /// <summary>
        /// 占位符
        /// </summary>
        public static readonly DependencyProperty PlaceholderProperty = DependencyProperty.Register(
            "Placeholder", typeof(string), typeof(TreeSelect), new FrameworkPropertyMetadata(""));

        public string Placeholder
        {
            get { return (string)GetValue(PlaceholderProperty); }
            set { SetValue(PlaceholderProperty, value); }
        }

        public static string GetPlaceholder(DependencyObject d)
        {
            return (string)d.GetValue(PlaceholderProperty);
        }

        public static void SetPlaceholder(DependencyObject obj, string value)
        {
            obj.SetValue(PlaceholderProperty, value);
        }
        #endregion

        #region CornerRadiusProperty Border圆角
        /// <summary>
        /// Border圆角
        /// </summary>
        public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.RegisterAttached(
            "CornerRadius", typeof(CornerRadius), typeof(TreeSelect), new FrameworkPropertyMetadata(new CornerRadius(3)));

        public CornerRadius CornerRadius
        {
            get { return (CornerRadius)GetValue(CornerRadiusProperty); }
            set { SetValue(CornerRadiusProperty, value); }
        }

        public static CornerRadius GetCornerRadius(DependencyObject d)
        {
            return (CornerRadius)d.GetValue(CornerRadiusProperty);
        }

        public static void SetCornerRadius(DependencyObject obj, CornerRadius value)
        {
            obj.SetValue(CornerRadiusProperty, value);
        }
        #endregion

        #region FocusBorderBrushProperty 焦点边框色，输入控件
        /// <summary>
        /// 焦点边框色，输入控件
        /// </summary>
        public static readonly DependencyProperty FocusBorderBrushProperty = DependencyProperty.Register(
            "FocusBorderBrush", typeof(Brush), typeof(TreeSelect), new FrameworkPropertyMetadata(null));

        public Brush FocusBorderBrush
        {
            get { return (Brush)GetValue(FocusBorderBrushProperty); }
            set { SetValue(FocusBorderBrushProperty, value); }
        }

        public static void SetFocusBorderBrush(DependencyObject element, Brush value)
        {
            element.SetValue(FocusBorderBrushProperty, value);
        }

        public static Brush GetFocusBorderBrush(DependencyObject element)
        {
            return (Brush)element.GetValue(FocusBorderBrushProperty);
        }
        #endregion

        #region MouseOverBorderBrushProperty 鼠标进入边框色，输入控件
        /// <summary>
        /// 鼠标进入边框色，输入控件
        /// </summary>
        public static readonly DependencyProperty MouseOverBorderBrushProperty = DependencyProperty.Register(
            "MouseOverBorderBrush", typeof(Brush), typeof(TreeSelect),
            new FrameworkPropertyMetadata(Brushes.Transparent,
            FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.Inherits));

        public Brush MouseOverBorderBrush
        {
            get { return (Brush)GetValue(MouseOverBorderBrushProperty); }
            set { SetValue(MouseOverBorderBrushProperty, value); }
        }

        /// <summary>
        /// Sets the brush used to draw the mouse over brush.
        /// </summary>
        public static void SetMouseOverBorderBrush(DependencyObject obj, Brush value)
        {
            obj.SetValue(MouseOverBorderBrushProperty, value);
        }

        /// <summary>
        /// Gets the brush used to draw the mouse over brush.
        /// </summary>
        public static Brush GetMouseOverBorderBrush(DependencyObject obj)
        {
            return (Brush)obj.GetValue(MouseOverBorderBrushProperty);
        }
        #endregion

        #region AllowClearProperty 清除输入框Text值按钮启用
        /// <summary>
        /// 清除输入框Text值按钮显示与否
        /// </summary>
        public static readonly DependencyProperty AllowClearProperty = DependencyProperty.RegisterAttached(
            "AllowClear", typeof(bool), typeof(TreeSelect), new FrameworkPropertyMetadata(false, AllowClearChanged));

        public bool AllowClear
        {
            get { return (bool)GetValue(AllowClearProperty); }
            set { SetValue(AllowClearProperty, value); }
        }

        /// <summary>
        /// Gets the brush used to draw the mouse over brush.
        /// </summary>
        public static bool GetAllowClear(DependencyObject d)
        {
            return (bool)d.GetValue(AllowClearProperty);
        }

        public static void SetAllowClear(DependencyObject obj, bool value)
        {
            obj.SetValue(AllowClearProperty, value);
        }

        private static void AllowClearChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (e.OldValue != e.NewValue && d is Button button)
            {
                button.CommandBindings.Add(RoutedUICommands.ClearTextCommandBinding);
            }
        }
        #endregion

        #region ShowSearchProperty 是否可搜索字段
        /// <summary>
        /// 是否可搜索字段
        /// </summary>
        public static readonly DependencyProperty ShowSearchProperty = DependencyProperty.Register(
            "ShowSearch", typeof(bool), typeof(TreeSelect), new FrameworkPropertyMetadata(false));

        public bool ShowSearch
        {
            get { return (bool)GetValue(ShowSearchProperty); }
            set { SetValue(ShowSearchProperty, value); }
        }

        public static bool GetShowSearch(DependencyObject d)
        {
            return (bool)d.GetValue(ShowSearchProperty);
        }

        public static void SetShowSearch(DependencyObject obj, bool value)
        {
            obj.SetValue(ShowSearchProperty, value);
        }
        #endregion

        #region ShowSearchProperty 搜索关键字绑定
        /// <summary>
        /// 搜索关键字绑定
        /// </summary>
        public static readonly DependencyProperty SearchPathProperty = DependencyProperty.Register(
            "SearchPath", typeof(object), typeof(TreeSelect), new FrameworkPropertyMetadata(null));

        public object SearchPath
        {
            get { return GetValue(SearchPathProperty); }
            set { SetValue(SearchPathProperty, value); }
        }

        public static object GetSearchPath(DependencyObject d)
        {
            return d.GetValue(SearchPathProperty);
        }

        public static void SetSearchPath(DependencyObject obj, object value)
        {
            obj.SetValue(SearchPathProperty, value);
        }
        #endregion
    }
}
